#if ESP_ENABLE_ESPCONN
/******************************************************************************
 * Copyright 2013-2014 Espressif Systems (Wuxi)
 *
 * FileName: espconn_udp.c
 *
 * Description: udp proto interface
 *
 * Modification history:
 *     2014/3/31, v1.0 create this file.
*******************************************************************************/

#include "ets_sys.h"
#include "os_type.h"
//#include "os.h"

#include "lwip/inet.h"
#include "lwip/err.h"
#include "lwip/pbuf.h"
#include "lwip/mem.h"
#include "lwip/tcp_impl.h"
#include "lwip/udp.h"

#include "lwip/app/espconn_udp.h"

#ifdef MEMLEAK_DEBUG
static const char mem_debug_file[] ICACHE_RODATA_ATTR = __FILE__;
#endif

extern espconn_msg *plink_active;
extern uint8 default_interface;

enum send_opt { ESPCONN_SENDTO, ESPCONN_SEND };
static void ICACHE_FLASH_ATTR espconn_data_sentcb(struct espconn *pespconn) {
  if (pespconn == NULL) {
    return;
  }

  if (pespconn->sent_callback != NULL) {
    pespconn->sent_callback(pespconn);
  }
}

static void ICACHE_FLASH_ATTR espconn_data_sent(void *arg, enum send_opt opt) {
  espconn_msg *psent = arg;

  if (psent == NULL) {
    return;
  }

  if (psent->pcommon.cntr == 0) {
    psent->pespconn->state = ESPCONN_CONNECT;
    if (psent->pcommon.err == 0) espconn_data_sentcb(psent->pespconn);
  } else {
    if (opt == ESPCONN_SEND) {
      espconn_udp_sent(arg, psent->pcommon.ptrbuf, psent->pcommon.cntr);
    } else {
      espconn_udp_sendto(arg, psent->pcommon.ptrbuf, psent->pcommon.cntr);
    }
  }
}

/******************************************************************************
 * FunctionName : espconn_udp_sent
 * Description  : sent data for client or server
 * Parameters   : void *arg -- client or server to send
 * 				  uint8* psent -- Data to send
 *                uint16 length -- Length of data to send
 * Returns      : return espconn error code.
 * - ESPCONN_OK. Successful. No error occured.
 * - ESPCONN_MEM. Out of memory.
 * - ESPCONN_RTE. Could not find route to destination address.
 * - More errors could be returned by lower protocol layers.
*******************************************************************************/
err_t ICACHE_FLASH_ATTR
espconn_udp_sent(void *arg, uint8 *psent, uint16 length) {
  espconn_msg *pudp_sent = arg;
  struct udp_pcb *upcb = pudp_sent->pcommon.pcb;
  struct pbuf *p, *q, *p_temp;
  u8_t *data = NULL;
  u16_t cnt = 0;
  u16_t datalen = 0;
  u16_t i = 0;
  err_t err;
  LWIP_DEBUGF(ESPCONN_UDP_DEBUG,
              ("espconn_udp_sent %d %d %p\n", __LINE__, length, upcb));

  if (pudp_sent == NULL || upcb == NULL || psent == NULL || length == 0) {
    return ESPCONN_ARG;
  }

  if ((IP_FRAG_MAX_MTU - 20 - 8) < length) {
    datalen = IP_FRAG_MAX_MTU - 20 - 8;
  } else {
    datalen = length;
  }

  p = pbuf_alloc(PBUF_TRANSPORT, datalen, PBUF_RAM);
  LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("espconn_udp_sent %d %p\n", __LINE__, p));

  if (p != NULL) {
    q = p;

    while (q != NULL) {
      data = (u8_t *) q->payload;
      LWIP_DEBUGF(ESPCONN_UDP_DEBUG,
                  ("espconn_udp_sent %d %p\n", __LINE__, data));

      for (i = 0; i < q->len; i++) {
        data[i] = ((u8_t *) psent)[cnt++];
      }

      q = q->next;
    }
  } else {
    return ESPCONN_MEM;
  }

  upcb->remote_port = pudp_sent->pespconn->proto.udp->remote_port;
  IP4_ADDR(&upcb->remote_ip, pudp_sent->pespconn->proto.udp->remote_ip[0],
           pudp_sent->pespconn->proto.udp->remote_ip[1],
           pudp_sent->pespconn->proto.udp->remote_ip[2],
           pudp_sent->pespconn->proto.udp->remote_ip[3]);

  LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("espconn_udp_sent %d %x %d\n", __LINE__,
                                  upcb->remote_ip, upcb->remote_port));

  struct netif *sta_netif = (struct netif *) eagle_lwip_getif(0x00);
  struct netif *ap_netif = (struct netif *) eagle_lwip_getif(0x01);

  if (wifi_get_opmode() == ESPCONN_AP_STA &&
      default_interface == ESPCONN_AP_STA && sta_netif != NULL &&
      ap_netif != NULL) {
    if (netif_is_up(sta_netif) && netif_is_up(ap_netif) &&
        ip_addr_isbroadcast(&upcb->remote_ip, sta_netif) &&
        ip_addr_isbroadcast(&upcb->remote_ip, ap_netif)) {
      p_temp = pbuf_alloc(PBUF_TRANSPORT, datalen, PBUF_RAM);
      if (pbuf_copy(p_temp, p) != ERR_OK) {
        LWIP_DEBUGF(ESPCONN_UDP_DEBUG,
                    ("espconn_udp_sent: copying to new pbuf failed\n"));
        return ESPCONN_ARG;
      }
      netif_set_default(sta_netif);
      err = udp_send(upcb, p_temp);
      pbuf_free(p_temp);
      netif_set_default(ap_netif);
    }
  }
  err = udp_send(upcb, p);

  LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("espconn_udp_sent %d %d\n", __LINE__, err));

  if (p->ref != 0) {
    LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("espconn_udp_sent %d %p\n", __LINE__, p));
    pbuf_free(p);
    pudp_sent->pcommon.ptrbuf = psent + datalen;
    pudp_sent->pcommon.cntr = length - datalen;
    pudp_sent->pcommon.err = err;
    espconn_data_sent(pudp_sent, ESPCONN_SEND);
    if (err > 0) return ESPCONN_IF;
    return err;
  } else {
    pbuf_free(p);
    return ESPCONN_RTE;
  }
}

/******************************************************************************
 * FunctionName : espconn_udp_sendto
 * Description  : sent data for UDP
 * Parameters   : void *arg -- UDP to send
 * 				  uint8* psent -- Data to send
 *                uint16 length -- Length of data to send
 * Returns      : return espconn error code.
 * - ESPCONN_OK. Successful. No error occured.
 * - ESPCONN_MEM. Out of memory.
 * - ESPCONN_RTE. Could not find route to destination address.
 * - More errors could be returned by lower protocol layers.
*******************************************************************************/
err_t ICACHE_FLASH_ATTR
espconn_udp_sendto(void *arg, uint8 *psent, uint16 length) {
  espconn_msg *pudp_sent = arg;
  struct udp_pcb *upcb = pudp_sent->pcommon.pcb;
  struct espconn *pespconn = pudp_sent->pespconn;
  struct pbuf *p, *q, *p_temp;
  struct ip_addr dst_ip;
  u16_t dst_port;
  u8_t *data = NULL;
  u16_t cnt = 0;
  u16_t datalen = 0;
  u16_t i = 0;
  err_t err;
  LWIP_DEBUGF(ESPCONN_UDP_DEBUG,
              ("espconn_udp_sent %d %d %p\n", __LINE__, length, upcb));

  if (pudp_sent == NULL || upcb == NULL || psent == NULL || length == 0) {
    return ESPCONN_ARG;
  }

  if ((IP_FRAG_MAX_MTU - 20 - 8) < length) {
    datalen = IP_FRAG_MAX_MTU - 20 - 8;
  } else {
    datalen = length;
  }

  p = pbuf_alloc(PBUF_TRANSPORT, datalen, PBUF_RAM);
  LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("espconn_udp_sent %d %p\n", __LINE__, p));

  if (p != NULL) {
    q = p;

    while (q != NULL) {
      data = (u8_t *) q->payload;
      LWIP_DEBUGF(ESPCONN_UDP_DEBUG,
                  ("espconn_udp_sent %d %p\n", __LINE__, data));

      for (i = 0; i < q->len; i++) {
        data[i] = ((u8_t *) psent)[cnt++];
      }

      q = q->next;
    }
  } else {
    return ESPCONN_MEM;
  }

  dst_port = pespconn->proto.udp->remote_port;
  IP4_ADDR(&dst_ip, pespconn->proto.udp->remote_ip[0],
           pespconn->proto.udp->remote_ip[1], pespconn->proto.udp->remote_ip[2],
           pespconn->proto.udp->remote_ip[3]);
  LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("espconn_udp_sent %d %x %d\n", __LINE__,
                                  upcb->remote_ip, upcb->remote_port));

  struct netif *sta_netif = (struct netif *) eagle_lwip_getif(0x00);
  struct netif *ap_netif = (struct netif *) eagle_lwip_getif(0x01);

  if (wifi_get_opmode() == ESPCONN_AP_STA &&
      default_interface == ESPCONN_AP_STA && sta_netif != NULL &&
      ap_netif != NULL) {
    if (netif_is_up(sta_netif) && netif_is_up(ap_netif) &&
        ip_addr_isbroadcast(&upcb->remote_ip, sta_netif) &&
        ip_addr_isbroadcast(&upcb->remote_ip, ap_netif)) {
      p_temp = pbuf_alloc(PBUF_TRANSPORT, datalen, PBUF_RAM);
      if (pbuf_copy(p_temp, p) != ERR_OK) {
        LWIP_DEBUGF(ESPCONN_UDP_DEBUG,
                    ("espconn_udp_sendto: copying to new pbuf failed\n"));
        return ESPCONN_ARG;
      }
      netif_set_default(sta_netif);
      err = udp_sendto(upcb, p_temp, &dst_ip, dst_port);
      pbuf_free(p_temp);
      netif_set_default(ap_netif);
    }
  }
  err = udp_sendto(upcb, p, &dst_ip, dst_port);

  if (p->ref != 0) {
    pbuf_free(p);
    pudp_sent->pcommon.ptrbuf = psent + datalen;
    pudp_sent->pcommon.cntr = length - datalen;
    pudp_sent->pcommon.err = err;
    espconn_data_sent(pudp_sent, ESPCONN_SENDTO);

    if (err > 0) return ESPCONN_IF;
    return err;
  } else {
    pbuf_free(p);
    return ESPCONN_RTE;
  }
}

/******************************************************************************
 * FunctionName : espconn_udp_server_recv
 * Description  : This callback will be called when receiving a datagram.
 * Parameters   : arg -- user supplied argument
 *                upcb -- the udp_pcb which received data
 *                p -- the packet buffer that was received
 *                addr -- the remote IP address from which the packet was
*received
 *                port -- the remote port from which the packet was received
 * Returns      : none
*******************************************************************************/
static void ICACHE_FLASH_ATTR
espconn_udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p,
                 struct ip_addr *addr, u16_t port) {
  espconn_msg *precv = arg;
  struct pbuf *q = NULL;
  u8_t *pdata = NULL;
  u16_t length = 0;
  struct ip_info ipconfig;

  LWIP_DEBUGF(ESPCONN_UDP_DEBUG,
              ("espconn_udp_server_recv %d %p\n", __LINE__, upcb));

  precv->pcommon.remote_ip[0] = ip4_addr1_16(addr);
  precv->pcommon.remote_ip[1] = ip4_addr2_16(addr);
  precv->pcommon.remote_ip[2] = ip4_addr3_16(addr);
  precv->pcommon.remote_ip[3] = ip4_addr4_16(addr);
  precv->pcommon.remote_port = port;
  precv->pcommon.pcb = upcb;

  if (wifi_get_opmode() != 1) {
    wifi_get_ip_info(1, &ipconfig);

    if (!ip_addr_netcmp(addr, &ipconfig.ip, &ipconfig.netmask)) {
      wifi_get_ip_info(0, &ipconfig);
    }
  } else {
    wifi_get_ip_info(0, &ipconfig);
  }

  precv->pespconn->proto.udp->local_ip[0] = ip4_addr1_16(&ipconfig.ip);
  precv->pespconn->proto.udp->local_ip[1] = ip4_addr2_16(&ipconfig.ip);
  precv->pespconn->proto.udp->local_ip[2] = ip4_addr3_16(&ipconfig.ip);
  precv->pespconn->proto.udp->local_ip[3] = ip4_addr4_16(&ipconfig.ip);

  if (p != NULL) {
    pdata = (u8_t *) os_zalloc(p->tot_len + 1);
    length = pbuf_copy_partial(p, pdata, p->tot_len, 0);
    precv->pcommon.pcb = upcb;
    pbuf_free(p);
    if (length != 0) {
      if (precv->pespconn->recv_callback != NULL) {
        precv->pespconn->recv_callback(precv->pespconn, pdata, length);
      }
    }
    os_free(pdata);
  } else {
    return;
  }
}

/******************************************************************************
 * FunctionName : espconn_udp_disconnect
 * Description  : A new incoming connection has been disconnected.
 * Parameters   : espconn -- the espconn used to disconnect with host
 * Returns      : none
*******************************************************************************/
void ICACHE_FLASH_ATTR espconn_udp_disconnect(espconn_msg *pdiscon) {
  if (pdiscon == NULL) {
    return;
  }

  struct udp_pcb *upcb = pdiscon->pcommon.pcb;

  udp_disconnect(upcb);

  udp_remove(upcb);

  espconn_list_delete(&plink_active, pdiscon);

  os_free(pdiscon);
  pdiscon = NULL;
}

/******************************************************************************
 * FunctionName : espconn_udp_server
 * Description  : Initialize the server: set up a PCB and bind it to the port
 * Parameters   : pespconn -- the espconn used to build server
 * Returns      : none
*******************************************************************************/
sint8 ICACHE_FLASH_ATTR espconn_udp_server(struct espconn *pespconn) {
  struct udp_pcb *upcb = NULL;
  espconn_msg *pserver = NULL;
  upcb = udp_new();

  if (upcb == NULL) {
    return ESPCONN_MEM;
  } else {
    pserver = (espconn_msg *) os_zalloc(sizeof(espconn_msg));

    if (pserver == NULL) {
      udp_remove(upcb);
      return ESPCONN_MEM;
    }

    pserver->pcommon.pcb = upcb;
    pserver->pespconn = pespconn;
    espconn_list_creat(&plink_active, pserver);
    udp_bind(upcb, IP_ADDR_ANY, pserver->pespconn->proto.udp->local_port);
    udp_recv(upcb, espconn_udp_recv, (void *) pserver);
    return ESPCONN_OK;
  }
}

/******************************************************************************
 * FunctionName : espconn_igmp_leave
 * Description  : leave a multicast group
 * Parameters   : host_ip -- the ip address of udp server
 * 				  multicast_ip -- multicast ip given by user
 * Returns      : none
*******************************************************************************/
sint8 ICACHE_FLASH_ATTR
espconn_igmp_leave(ip_addr_t *host_ip, ip_addr_t *multicast_ip) {
  if (igmp_leavegroup(host_ip, multicast_ip) != ERR_OK) {
    LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("udp_leave_multigrup failed!\n"));
    return -1;
  };

  return ESPCONN_OK;
}

/******************************************************************************
 * FunctionName : espconn_igmp_join
 * Description  : join a multicast group
 * Parameters   : host_ip -- the ip address of udp server
 * 				  multicast_ip -- multicast ip given by user
 * Returns      : none
*******************************************************************************/
sint8 ICACHE_FLASH_ATTR
espconn_igmp_join(ip_addr_t *host_ip, ip_addr_t *multicast_ip) {
  if (igmp_joingroup(host_ip, multicast_ip) != ERR_OK) {
    LWIP_DEBUGF(ESPCONN_UDP_DEBUG, ("udp_join_multigrup failed!\n"));
    return -1;
  };

  /* join to any IP address at the port  */
  return ESPCONN_OK;
}
#endif /* ESP_ENABLE_ESPCONN */
